﻿using Cuofrisp.Core.ParallelCore.Interfaces;
using MathNet.Symbolics;
using System.Diagnostics;

namespace Cuofrisp.Core.ParallelCore.Tools
{
    internal sealed class MetricInverseProvider : IMetricInverseProvider
    {
        private static bool IsJustZero(SymbolicExpression expression)
        {
            return expression.Type is SymbolicExpressionType.RationalNumber &&
                expression.RationalNumberValue.IsZero;
        }

        private static SymbolicExpression[,] Submatrix(
            SymbolicExpression[,] matrix, int rowToRemove, int colToRemove)
        {
            var length = matrix.GetLength(0);
            var lengthMinusOne = length - 1;
            var lengthRight = lengthMinusOne - colToRemove;
            var result = new SymbolicExpression[lengthMinusOne, lengthMinusOne];
            foreach (var copyingRow in Enumerable.Range(0, lengthMinusOne))
            {
                var destinationIndex = copyingRow * lengthMinusOne;
                var sourceIndex = destinationIndex + copyingRow;
                if (copyingRow >= rowToRemove)
                {
                    sourceIndex += length;
                }
                Array.Copy(matrix, sourceIndex, result, destinationIndex, colToRemove);

                sourceIndex += colToRemove + 1;
                destinationIndex += colToRemove;

                Array.Copy(matrix, sourceIndex, result, destinationIndex, lengthRight);
            }
            return result;
        }

        private SymbolicExpression Determinant(SymbolicExpression[,] matrix)
        {
            Debug.Assert(matrix.GetLength(0) == matrix.GetLength(1));

            if (matrix.Length == 1)
                return matrix[0, 0];

            var items = Enumerable.Range(0, matrix.GetLength(0)).Select(
                (i) => {
                    var current = matrix[i, 0];
                    if (IsJustZero(current))
                        return SymbolicExpression.Zero;
                    if (i % 2 == 0)
                        return current * this.Determinant(Submatrix(matrix, i, 0));
                    else
                        return -current * this.Determinant(Submatrix(matrix, i, 0));
                });

            var result = SymbolicExpression.Zero;
            foreach (var item in items)
            {
                result += item;
            }
            return result;
        }

        public required ParallelOptions ParallelOptions { get; init; }
        public void TrySetMetricInverse(
            CuofrispResult result, IExpressionSimplifier simplifier, out bool isNotFullRank)
        {
            var determinant = this.Determinant(result.Metric);
            _ = Parallel.For(0, result.MetricInverse.Length,
                this.ParallelOptions,
                (iJ) => {
                    var (i, j) = iJ.As2dIndex(result.VariableCount);
                    var submatrix = Submatrix(result.Metric, j, i);
                    if ((i + j) % 2 == 0)
                        result.MetricInverse[i, j] = this.Determinant(submatrix) / determinant;
                    else
                        result.MetricInverse[i, j] = -this.Determinant(submatrix) / determinant;
                });
            isNotFullRank = false;
        }
    }
}
